package org.zjvis.datascience.common.util;

import com.google.common.collect.Lists;
import org.zjvis.datascience.common.model.stat.Range;

import java.math.BigDecimal;
import java.util.List;

/**
 * @description 数值查询 聚合工具类
 * @date 2021-09-26
 */
public class NumericUtil {

    public static final int SEGMENT_LIMIT = 23;

    /**
     * 整数范围大小的序列
     */
    public static final long[] sequence = new long[]{25, 50, 100, 125, 250};

    public static final BigDecimal[] decimalSequence = new BigDecimal[]{new BigDecimal("25"),
            new BigDecimal("50"), new BigDecimal("100"), new BigDecimal("125"), new BigDecimal("250")};

    public static List<Range> divisionInteger(Integer min, Integer max, int segmentNum) {
        if (null == min || null == max) {
            return Lists.newArrayList();
        }
        List<Range> ranges = divisionLong(Long.valueOf(min), Long.valueOf(max), segmentNum);
        return ranges;
    }

    public static List<Range> divisionLong(Long min, Long max, int segmentNum) {
        if (null == min || null == max) {
            return Lists.newArrayList();
        }
        long range = max - min + 1;
        long step = range / segmentNum + 1;
        step = arrangementStep(step);
        long lower = min / step * step;
        lower = lower > min ? lower - step : lower;
        List<Range> segments = Lists.newArrayList();
        while (true) {
            long upper = lower + step;
            Range segment = Range.closedOpen(lower, lower + step);
            segments.add(segment);
            lower = upper;
            if (upper > max) {
                break;
            }
        }
        return segments;
    }

    public static List<Range> divisionDouble(Double min, Double max, int segmentNum) {
        if (null == min || null == max) {
            return Lists.newArrayList();
        }
        List<Range> ranges = divisionBigDecimal(new BigDecimal(min.toString()),
                new BigDecimal(max.toString()), segmentNum);
        List segments = Lists.newArrayList();
        for (Range range : ranges) {
            BigDecimal bigDecimalMin = (BigDecimal) range.lowerEndpoint();
            BigDecimal bigDecimalMax = (BigDecimal) range.upperEndpoint();
            Range segment = Range
                    .closedOpen(bigDecimalMin.doubleValue(),
                            bigDecimalMax.doubleValue());
            segments.add(segment);
        }
        return segments;
    }

    public static List<Range> divisionBigDecimal(BigDecimal min, BigDecimal max, int segmentNum) {
        if (null == min || null == max) {
            return Lists.newArrayList();
        }
        min = min.stripTrailingZeros();//去掉末尾多余的0
        max = max.stripTrailingZeros();
        int scalePlus = Math.max(min.scale(), max.scale()) + 3;//多加3位精度
        min = min.movePointRight(scalePlus);
        max = max.movePointRight(scalePlus);
        List<Range> ranges = divisionDecimal(min, max, segmentNum);
        List<Range> segments = Lists.newArrayList();
        for (Range range : ranges) {
            BigDecimal longMin = (BigDecimal) range.lowerEndpoint();
            BigDecimal longMax = (BigDecimal) range.upperEndpoint();
            Range segment = Range
                    .closedOpen(longMin.movePointLeft(scalePlus).stripTrailingZeros(),
                            longMax.movePointLeft(scalePlus).stripTrailingZeros());
            segments.add(segment);
        }
        return segments;
    }

    private static List<Range> divisionDecimal(BigDecimal min, BigDecimal max, int segmentNum) {
        BigDecimal range = max.subtract(min).add(new BigDecimal("1"));
        BigDecimal step = range.divide(new BigDecimal(segmentNum), 0, BigDecimal.ROUND_DOWN).add(new BigDecimal("1"));
        step = arrangementDecimalStep(step);
        BigDecimal lower = min.divide(step, 0, BigDecimal.ROUND_DOWN).multiply(step);
        lower = lower.compareTo(min) > 0 ? lower.subtract(step) : lower;
        List<Range> segments = Lists.newArrayList();
        while (true) {
            BigDecimal upper = lower.add(step);
            Range segment = Range.closedOpen(lower, lower.add(step));
            segments.add(segment);
            lower = upper;
            if (upper.compareTo(max) > 0) {
                break;
            }
        }
        return segments;
    }

    /**
     * 步长按序列取整BigDecimal
     *
     * @param step
     * @return
     */
    private static BigDecimal arrangementDecimalStep(BigDecimal step) {
        if (step.compareTo(new BigDecimal("5")) <= 0) {
            return step;
        } else if (step.compareTo(new BigDecimal("10")) <= 0) {
            return new BigDecimal("10");
        } else if (step.compareTo(new BigDecimal("20")) <= 0) {
            return new BigDecimal("20");
        } else {
            BigDecimal multiple = new BigDecimal("1");
            while (true) {
                for (BigDecimal i : decimalSequence) {
                    BigDecimal temp = i.multiply(multiple);
                    if (step.compareTo(i.multiply(multiple)) < 0) {
                        return temp;
                    }
                }
                multiple = multiple.multiply(new BigDecimal("10"));
            }
        }
    }

    /**
     * 步长按序列取整
     *
     * @param step
     * @return
     */
    private static long arrangementStep(long step) {
        if (step <= 5) {
            return step;
        } else if (step <= 10) {
            return 10;
        } else if (step <= 20) {
            return 20;
        } else {
            long multiple = 1;
            while (true) {
                for (long i : sequence) {
                    if (step < i * multiple) {
                        return i * multiple;
                    }
                }
                multiple = multiple * 10;
            }
        }
    }
}
